//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.InteropServices;
using OpenLS.Spreadsheet.Formats.Biff;
using OpenLS.Spreadsheet.Formats.Biff.Records;

namespace OpenLS.Spreadsheet.Formats
{
    ///<summary>
    /// Provides access to legacy (xls) files
    ///</summary>
    public sealed partial class BiffFormat : SpreadsheetFormat
    {

#if DEBUG
        public static void ShowUnhandledTypes()
        {
            return;
            List<string> types = new List<string>();
            foreach (BiffRecordType obj in Enum.GetValues(typeof (BiffRecordType)))
            {
                var record = BiffRecordFactory.NewRecord(obj);
                if (record is UnhandledRecord)
                    types.Add(obj.ToString());

            }
            types.Sort();
            foreach (var s in types)
            {
                Debug.WriteLine(s);
            }
        }
#endif

        /// <summary>
        /// Open a spreadsheet document from a file
        /// </summary>
        /// <param name="stream">The stream to read</param>
        /// <returns></returns>
        public override SpreadsheetDocument Open(Stream stream)
        {
            var tempFile = Path.GetTempFileName();
            using (var ss = File.Open(tempFile, FileMode.Open))
            {
                byte[] data = new byte[stream.Length];
                stream.Read(data, 0, data.Length);
                ss.Write(data, 0, data.Length);
            }
            var result = Open(tempFile);
            File.Delete(tempFile);
            return result;
        }

        public override void Save(SpreadsheetDocument document, Stream stream)
        {
            var tempFile = Path.GetTempFileName();
            Save(document, tempFile);
            using (var ss = File.Open(tempFile, FileMode.Open))
            {
                byte[] data = new byte[ss.Length];
                ss.Read(data, 0, data.Length);
                stream.Write(data, 0, data.Length);
            }
            File.Delete(tempFile);
        }

        public override string ClipboardName
        {
            get { return SpreadsheetDataFormats.Biff8; }
        }

        public override IEnumerable<string> Extensions
        {
            get { yield return "xls"; }
        }

        /// <summary>
        /// Save the spreadsheet document to file using the current format
        /// </summary>
        /// <param name="document">The document to save</param>
        /// <param name="fileName">The file name to save to</param>
        public override void Save(SpreadsheetDocument document, string fileName)
        {
            Contract.Requires(document != null);
#if DEBUG
            Contract.Requires(document.Workbook.IsValid);
#endif
            using (var storage = Storage.Create(fileName))
            {
                WorkbookSequencer sequencer = new WorkbookSequencer();
                /*foreach (var record in WorkbookSequencer.GetWorkbookRecords(new BiffWriterWorkbookContext(document.Workbook, null)))
                {
                 //   Console.WriteLine(record);
                }*/
#if DEBUG2
                Contract.Assert(document.Workbook.IsValid);
                var allRecords = new List<XFRecord>(Enumerable.OfType<XFRecord>(WorkbookSequencer.GetWorkbookRecords(new BiffWriterWorkbookContext(document.Workbook, null))));
                var allDistinctRecords = new List<XFRecord>(Enumerable.Distinct(allRecords));
                //\\  Contract.Assert(allDistinctRecords.Count == allRecords.Count);

#endif
                using (var stream = storage.CreateStream("Workbook", FileAccess.ReadWrite))
                {
                    MemoryStream memoryStream = new MemoryStream();
                    using (var binaryWriter = new BinaryWriter(memoryStream))
                    {
                        BiffWriterWorkbookContext ctx = new BiffWriterWorkbookContext(document.Workbook, binaryWriter);
                        WorkbookSequencer workbookSequencer = new WorkbookSequencer();
                        foreach (var record in workbookSequencer.GetWorkbookRecords(ctx))
                        {
                            Contract.Assert(!(record is IgnoredBiffRecord));
                            record.Write(ctx);
                        }
                        binaryWriter.Flush();
                        memoryStream.Position = 0;
                        byte[] data = new byte[memoryStream.Length];
                        memoryStream.Read(data, 0, data.Length);
                        stream.Write(data, 0, data.Length);
                    }
                }
            }
        }

        ///<summary>
        /// Open an xls file from a file name.
        ///</summary>
        ///<param name="fileName">Name of the file to open</param>
        ///<returns></returns>
        ///<exception cref="ApplicationException"></exception>
        public override SpreadsheetDocument Open(string fileName)
        {
#if DEBUG
            Contract.Ensures(Contract.Result<SpreadsheetDocument>().Workbook.IsValid);

#endif
            using (
                var storage = Storage.Open(fileName))
            {
                Stream stream = null;
                try
                {
                    stream = storage.OpenStream("Workbook", FileAccess.Read);
                }
                catch (COMException e)
                {
                    if (stream != null)
                        stream.Dispose();
                    throw new ApplicationException("Unable to find workbook stream", e);
                }
                long l = stream.Length;
                var bytes = new byte[l];
                stream.Read(bytes, 0, (int) l);
                var mStream = new MemoryStream(bytes);
                var reader = new BiffRecordReader(new BiffReaderContext(new BinaryReader(mStream)));
                var result = new SpreadsheetDocument(SpreadsheetContext.Default);
                result.Workbook.Sheets.Clear();
                using (result.Workbook.CreateUndoManagerDisabler())
                {
                    using (result.Workbook.CreateLoadingCalculationDisabler())
                    {
#if DEBUG
                    Contract.Assert(result.Workbook.IsValid);
#endif
                        readDocument(result.Workbook, reader);
#if DEBUG2
                    Contract.Assert(result.Workbook.IsValid);
#endif
                    }

                }
                result.FileName = fileName;
                result.SetFormat(this);
#if DEBUG2
                WorkbookSequencer.CheckWorkbook(result.Workbook);
#endif
                return result;
            }
        }

#if DEBUG
#endif

        private static void readDocument(Workbook workbook, BiffRecordReader reader)
        {
            var ctx = new BiffReaderWorkbookContext(workbook);
            foreach (BiffRecord record in reader.GetRecords())
            {
                if (record is IWorkbookPass1Record)
                    ((IWorkbookPass1Record) record).Apply(ctx);
            }
            BiffRecord r = reader.ReadRecord();
            Sheet lastWorksheet = null;
            int worksheetIndex = 0;
            while (r != null)
            {
                if (r is BofRecord)
                {
                    var bofr = (BofRecord) r;
                    switch (bofr.GroupType)
                    {
                        case BofRecord.BofRecordType.Workbook:
                            break;
                        case BofRecord.BofRecordType.Worksheet:
                            BoundSheetRecord boundSheetRecord = ctx.BoundSheets[workbook.Sheets.Count];
                            lastWorksheet = workbook.AddWorksheet();
                            boundSheetRecord.ApplyToSheet(lastWorksheet, ctx);
                            ctx.WorksheetIndex++;
                            readWorksheet((Worksheet) lastWorksheet, reader, ctx);
                            break;
                        case BofRecord.BofRecordType.Chart:
                            BoundSheetRecord boundSheetRecord2 = ctx.BoundSheets[workbook.Sheets.Count];
                            lastWorksheet = workbook.AddChartsheet();
                            boundSheetRecord2.ApplyToSheet(lastWorksheet, ctx);
                            ctx.WorksheetIndex++;
                            readOtherSheet((Chartsheet) lastWorksheet, reader, ctx);
                            break;
                        case BofRecord.BofRecordType.VBModule:
                            {
                                BoundSheetRecord boundSheetRecord3 = ctx.BoundSheets[workbook.Sheets.Count];
                                ctx.WorksheetIndex++;
                                lastWorksheet = workbook.AddVbModule();
                                boundSheetRecord3.ApplyToSheet(lastWorksheet, ctx);
                                readOtherSheet((VbModule) lastWorksheet, reader, ctx);
                                break;
                            }
                        case BofRecord.BofRecordType.Excel4Macro:
                            {
                                BoundSheetRecord boundSheetRecord3 = ctx.BoundSheets[workbook.Sheets.Count];
                                ctx.WorksheetIndex++;
                                lastWorksheet = workbook.AddVbModule(); //\\
                                boundSheetRecord3.ApplyToSheet(lastWorksheet, ctx);
                                readOtherSheet((VbModule) lastWorksheet, reader, ctx);
                                break;
                            }
                        case BofRecord.BofRecordType.Workspace:
                            throw new NotImplementedException();

                        default:
                            throw new NotSupportedException();
                    }
                }
                if (r is IWorkbookPass2Record)
                    ((IWorkbookPass2Record) r).Apply(ctx);
                if (r is IWorksheetLevelRecord && (lastWorksheet as Worksheet) != null)
                    ((IWorksheetLevelRecord) r).Apply(new BiffReaderWorksheetContext(ctx, (Worksheet) lastWorksheet));
                r = reader.ReadRecord();
            }

            using (new WorkbookCalculationDisabler(workbook))
            {
                foreach (ActionType type in new[]
                                                {
                                                    ActionType.Sheet, ActionType.DefinedName,
                                                    ActionType.ResolveExternalName,
                                                    ActionType.ResolveName, ActionType.SharedFormula,
                                                    ActionType.Formulas, ActionType.View,
                                                    ActionType.ConditionalFormatting
                                                })
                {
                    if (type == ActionType.DefinedName)
                        workbook.RecalcOnNewName = false;
                    foreach (var action in ctx.GetActions(type))
                        action(null);
                    if (type == ActionType.DefinedName)
                        workbook.RecalcOnNewName = true;
                }
            }
            foreach (Cell cell in ctx.ToRecalculate)
                cell.ChainRecalc();
        }

        private static void readOtherSheet<T>(T worksheet, BiffRecordReader reader, BiffReaderWorkbookContext context)
            where T : Sheet
        {
            BiffRecord previousRecord = reader.ReadRecord();
            while (previousRecord != null)
            {
                if (previousRecord is EofRecord)
                    return;
                BiffRecord r = reader.ReadRecord();
                if (r != null)
                {
                }
                previousRecord = r;
            }
        }

        private static void readWorksheet(Worksheet worksheet, BiffRecordReader reader, BiffReaderWorkbookContext ctx)
        {
            using (var wsContext = new BiffReaderWorksheetContext(ctx, worksheet) {PreviousRecord = reader.ReadRecord()}
                )
            {
                using (worksheet.CreateWorksheetPanesDisabler())
                {
                    using (worksheet.CreateWorksheetCellPropertyEventDisabler())
                    {
                        using (worksheet.CreateWorksheetCellAddedEventDisabler())
                        {
                            while (wsContext.PreviousRecord != null)
                            {
                                if (wsContext.PreviousRecord is EofRecord)
                                    return;

                                BiffRecord r = reader.ReadRecord();
                                if (r != null)
                                {
                                    if (r is IWorksheetLevelRecord)
                                    {
                                        ((IWorksheetLevelRecord) r).Apply(wsContext);
                                    }
                                    if (r is BofRecord)
                                    {
                                        var bof = (BofRecord) r;
                                        if (bof.GroupType == BofRecord.BofRecordType.Chart)
                                        {
                                            readOtherSheet((Chartsheet) null, reader, ctx);
                                        }
                                    }
                                }
                                wsContext.PreviousRecord = r;
                            }
                        }
                    }
                }
            }
        }
    }
}